/*
 * Lpc port to Java.
 * Copyright (C) 1999  Christopher Edwards
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
package javaforce.codec.gsm;

public class Lpc {

  private int L_ACF[] = new int[9];

  public void Gsm_LPC_Analysis(
          short[] so, /* 0..159 signals       IN/OUT  */
          short[] LARc) /* 0..7   LARc's        OUT     */ {
    Autocorrelation(so);
    Reflection_coefficients(LARc);
    Transformation_to_Log_Area_Ratios(LARc);
    Quantization_and_coding(LARc);
  }

  private void Autocorrelation(short[] so) /* [0..159]  IN/OUT  */
          throws IllegalArgumentException {
    int i = 0, sp_index = 0;
    short temp = 0, smax = 0, scalauto = 0;
    int L_temp = 0;
    int L_temp2 = 0;

    /*  Dynamic scaling of the array  s[0..159]
     */

 /*  Search for the maximum.
     */
    for (int k = 0; k <= 159; k++) {
      temp = Add.GSM_ADD(so[k], (short) 0);
      if (temp > smax) {
        smax = temp;
      }
    }

    /*  Computation of the scaling factor.
     */
    if (smax == 0) {
      scalauto = 0;
    } else {
      if (!(smax > 0)) {
        throw new IllegalArgumentException("Autocorrelation: smax = "
                + smax + " should be > 0.");
      }
      scalauto = (short) (4 - Add.gsm_norm((int) (smax << 16)));
      /* sub(4,..) */
    }

    /*  Scaling of the array s[0...159]
     */
    if (scalauto > 0) {
      if (!(scalauto <= 4)) {
        throw new IllegalArgumentException("Autocorrelation: scalauto = "
                + scalauto + " should be <= 4.");
      }
      switch (scalauto) {
        case 1:
          for (int k = 0; k <= 159; k++) {
            so[k] = Add.GSM_MULT_R(so[k], (short) 16384);
          }
          break;

        case 2:
          for (int k = 0; k <= 159; k++) {
            so[k] = Add.GSM_MULT_R(so[k],
                    (short) (16384 >> 1));
          }
          break;

        case 3:
          for (int k = 0; k <= 159; k++) {
            so[k] = Add.GSM_MULT_R(so[k],
                    (short) (16384 >> 2));
          }
          break;

        case 4:
          for (int k = 0; k <= 159; k++) {
            so[k] = Add.GSM_MULT_R(so[k],
                    (short) (16384 >> 3));
          }
          break;
      }
    }

    /*  Compute the L_ACF[..].
     */
    short[] sp = so;
    short sl = sp[sp_index];

    // Zero out L_ACF
    int[] temp_arr = {0, 0, 0, 0, 0, 0, 0, 0, 0};
    System.arraycopy(temp_arr, 0, L_ACF, 0, L_ACF.length);

    L_ACF[0] += (int) (sl * sp[(sp_index - 0)]);

    sl = sp[++sp_index];
    L_ACF[0] += (int) (sl * sp[(sp_index - 0)]);
    L_ACF[1] += (int) (sl * sp[(sp_index - 1)]);

    sl = sp[++sp_index];
    L_ACF[0] += (int) (sl * sp[(sp_index - 0)]);
    L_ACF[1] += (int) (sl * sp[(sp_index - 1)]);
    L_ACF[2] += (int) (sl * sp[(sp_index - 2)]);

    sl = sp[++sp_index];
    L_ACF[0] += (int) (sl * sp[(sp_index - 0)]);
    L_ACF[1] += (int) (sl * sp[(sp_index - 1)]);
    L_ACF[2] += (int) (sl * sp[(sp_index - 2)]);
    L_ACF[3] += (int) (sl * sp[(sp_index - 3)]);

    sl = sp[++sp_index];
    L_ACF[0] += (int) (sl * sp[(sp_index - 0)]);
    L_ACF[1] += (int) (sl * sp[(sp_index - 1)]);
    L_ACF[2] += (int) (sl * sp[(sp_index - 2)]);
    L_ACF[3] += (int) (sl * sp[(sp_index - 3)]);
    L_ACF[4] += (int) (sl * sp[(sp_index - 4)]);

    sl = sp[++sp_index];
    L_ACF[0] += (int) (sl * sp[(sp_index - 0)]);
    L_ACF[1] += (int) (sl * sp[(sp_index - 1)]);
    L_ACF[2] += (int) (sl * sp[(sp_index - 2)]);
    L_ACF[3] += (int) (sl * sp[(sp_index - 3)]);
    L_ACF[4] += (int) (sl * sp[(sp_index - 4)]);
    L_ACF[5] += (int) (sl * sp[(sp_index - 5)]);

    sl = sp[++sp_index];
    L_ACF[0] += (int) (sl * sp[(sp_index - 0)]);
    L_ACF[1] += (int) (sl * sp[(sp_index - 1)]);
    L_ACF[2] += (int) (sl * sp[(sp_index - 2)]);
    L_ACF[3] += (int) (sl * sp[(sp_index - 3)]);
    L_ACF[4] += (int) (sl * sp[(sp_index - 4)]);
    L_ACF[5] += (int) (sl * sp[(sp_index - 5)]);
    L_ACF[6] += (int) (sl * sp[(sp_index - 6)]);

    sl = sp[++sp_index];
    L_ACF[0] += (int) (sl * sp[(sp_index - 0)]);
    L_ACF[1] += (int) (sl * sp[(sp_index - 1)]);
    L_ACF[2] += (int) (sl * sp[(sp_index - 2)]);
    L_ACF[3] += (int) (sl * sp[(sp_index - 3)]);
    L_ACF[4] += (int) (sl * sp[(sp_index - 4)]);
    L_ACF[5] += (int) (sl * sp[(sp_index - 5)]);
    L_ACF[6] += (int) (sl * sp[(sp_index - 6)]);
    L_ACF[7] += (int) (sl * sp[(sp_index - 7)]);

    sl = sp[++sp_index];

    for (i = sp_index; i < 160; ++i) {

      sl = sp[i];

      L_ACF[0] += (int) (sl * sp[(i - 0)]);
      L_ACF[1] += (int) (sl * sp[(i - 1)]);
      L_ACF[2] += (int) (sl * sp[(i - 2)]);
      L_ACF[3] += (int) (sl * sp[(i - 3)]);
      L_ACF[4] += (int) (sl * sp[(i - 4)]);
      L_ACF[5] += (int) (sl * sp[(i - 5)]);
      L_ACF[6] += (int) (sl * sp[(i - 6)]);
      L_ACF[7] += (int) (sl * sp[(i - 7)]);
      L_ACF[8] += (int) (sl * sp[(i - 8)]);
    }

    for (int k = 0; k < 9; k++) {
      L_ACF[k] <<= 1;
    }

    /*   Rescaling of the array s[0..159]
     */
    if (scalauto > 0) {
      if (!(scalauto <= 4)) {
        throw new IllegalArgumentException("Autocorrelation: scalauto = "
                + scalauto + " should be <= 4.");
      }

      for (int k = 0; k < 160; k++) {
        so[k] <<= scalauto;
      }
    }
  }

  private void Reflection_coefficients(short[] r /* 0...7   OUT */)
          throws IllegalArgumentException {
    short temp = 0;
    int ltmp = 0;
    int r_index = 0;

    short[] ACF = new short[9];
    /* 0..8 */
    short[] P = new short[9];
    /* 0..8 */
    short[] K = new short[9];
    /* 2..8 */

 /*  Schur recursion with 16 bits arithmetic.
     */

    if (L_ACF[0] == 0) {
      /* everything is the same. */
      for (int i = 0; i < 8; i++) {
        r[i] = 0;
      }
      return;
    }

    if (L_ACF[0] == 0) {
      throw new IllegalArgumentException("Reflection_coefficients: L_ACF[0] = "
              + L_ACF[0] + " should not = 0.");
    }

    temp = Add.gsm_norm(L_ACF[0]);

    if (!(temp >= 0 && temp < 32)) {
      throw new IllegalArgumentException("Reflection_coefficients: temp = "
              + temp + " should be >= 0 and < 32.");
    }


    /* ? overflow ? */
    for (int i = 0; i <= 8; i++) {
      ACF[i] = Add.SASR(L_ACF[i] << temp, 16);
    }

    /*   Initialize array P[..] and K[..] for the recursion.
     */
    System.arraycopy(ACF, 0, K, 0, 7);

    System.arraycopy(ACF, 0, P, 0, 8);

    /*   Compute reflection coefficients
     */
    for (int n = 1; n <= 8; n++, r_index++) {

      temp = P[1];
      temp = Add.GSM_ABS(temp);
      if (P[0] < temp) {
        for (int i = n; i < 8; i++) {
          r[i] = 0;
        }
        return;
      }

      r[r_index] = Add.gsm_div(temp, P[0]);

      if (!(r[r_index] >= 0)) {
        throw new IllegalArgumentException("Reflection_coefficients: r[" + r_index + "] = "
                + r[r_index] + " should be >= 0");
      }

      if (P[1] > 0) {
        /* r[n] = sub(0, r[n]) */
        r[r_index] = (short) (-(r[r_index]));
      }

      if (r[r_index] == Gsm_Def.MIN_WORD) {
        throw new IllegalArgumentException("Reflection_coefficients: r[" + r_index + "] = "
                + r[r_index] + " should not be " + Gsm_Def.MIN_WORD);
      }
      if (n == 8) {
        return;
      }

      /*  Schur recursion
       */
      temp = Add.GSM_MULT_R(P[1], r[r_index]);
      P[0] = Add.GSM_ADD(P[0], temp);

      for (int m = 1; m <= 8 - n; m++) {
        temp = Add.GSM_MULT_R(K[m], r[r_index]);
        P[m] = Add.GSM_ADD(P[m + 1], temp);

        temp = Add.GSM_MULT_R(P[m + 1], r[r_index]);
        K[m] = Add.GSM_ADD(K[m], temp);
      }
    }
  }

  /* 4.2.6 */

 /*
	 *  The following scaling for r[..] and LAR[..] has been used:
	 *
	 *  r[..]   = integer( real_r[..]*32768. ); -1 <= real_r < 1.
	 *  LAR[..] = integer( real_LAR[..] * 16384 );
	 *  with -1.625 <= real_LAR <= 1.625
   */
  private void Transformation_to_Log_Area_Ratios(short[] r /* 0..7 IN/OUT */)
          throws IllegalArgumentException {
    short temp;

    /* Computation of the LAR[0..7] from the r[0..7]
     */
    for (int i = 0; i < 8; i++) {

      temp = r[i];
      temp = Add.GSM_ABS(temp);

      if (!(temp >= 0)) {
        throw new IllegalArgumentException("Transformation_to_Log_Area_Ratios: temp = "
                + temp + " should be >= 0 ");
      }

      if (temp < 22118) {
        temp >>= 1;
      } else if (temp < 31130) {

        if (!(temp >= 11059)) {
          throw new IllegalArgumentException("Transformation_to_Log_Area_Ratios: temp = "
                  + temp + " should be >= 11059 ");
        }

        temp -= 11059;
      } else {
        if (!(temp >= 26112)) {
          throw new IllegalArgumentException("Transformation_to_Log_Area_Ratios: temp = "
                  + temp + " should be >= 26112 ");
        }

        temp -= 26112;
        temp <<= 2;
      }

      r[i] = (short) (r[i] < 0 ? -temp : temp);

      if (r[i] == Gsm_Def.MIN_WORD) {
        throw new IllegalArgumentException("Transformation_to_Log_Area_Ratios: r[" + i + "] = "
                + r[i] + " should not be = " + Gsm_Def.MIN_WORD);
      }
    }
  }

  /* 4.2.7 */

 /*  This procedure needs four tables; the following equations
	 *  give the optimum scaling for the constants:
	 *
	 *  A[0..7] = integer( real_A[0..7] * 1024 )
	 *  B[0..7] = integer( real_B[0..7] *  512 )
	 *  MAC[0..7] = maximum of the LARc[0..7]
	 *  MIC[0..7] = minimum of the LARc[0..7]
   */
  private void Quantization_and_coding(short[] LAR /* [0..7] IN/OUT  */) {
    int index = 0;

    STEP2(20480, 0, 31, -32, LAR, index++);
    STEP2(20480, 0, 31, -32, LAR, index++);
    STEP2(20480, 2048, 15, -16, LAR, index++);
    STEP2(20480, -2560, 15, -16, LAR, index++);

    STEP2(13964, 94, 7, -8, LAR, index++);
    STEP2(15360, -1792, 7, -8, LAR, index++);
    STEP2(8534, -341, 3, -4, LAR, index++);
    STEP2(9036, -1144, 3, -4, LAR, index++);

  }

  private void STEP2(int A, int B, int MAC, int MIC,
          short[] LAR, int index) {
    short temp = 0;

    temp = Add.GSM_MULT((short) A, LAR[index]);
    temp = Add.GSM_ADD(temp, (short) B);
    temp = Add.GSM_ADD(temp, (short) 256);
    temp = Add.SASR(temp, 9);
    LAR[index] = (short) (temp > MAC ? MAC - MIC
            : (temp < MIC ? 0 : temp - MIC));
  }
}
